package com.gitee.vueantd.common.utils.download;

/**
 * @Author:
 * @Date: 2021/3/23 10:02
 * @Description:
 */

public class BandWidthLimiter {
  /*  KB  */
  private static Long KB = 1024L;

  /*  The smallest count chunk length in bytes  */
  private static Long CHUNK_LENGTH = 32768L;

  /*  How many bytes will be sent or receive  */
  private int bytesWillBeSentOrReceive = 0;

  /*  When the last piece was sent or receive  */
  private long lastPieceSentOrReceiveTick = System.nanoTime();

  /*  Default rate is 1024KB/s  */
  private int maxRate = 1024;

  /*  Time cost for sending CHUNK_LENGTH bytes in nanoseconds  */
  private long timeCostPerChunk = (1000000000l * CHUNK_LENGTH)
          / (this.maxRate * KB);

  /**
   * 30       * Initialize a BandwidthLimiter object with a certain rate.
   * 31       *
   * 32       *  @param  maxRate
   * 33       *            the download or upload speed in KBytes
   * 34
   */
  public BandWidthLimiter(int maxRate) {
    this.setMaxRate(maxRate);
  }

  public int getMaxRate() {
    return this.maxRate;
  }

  /**
   * 40       * Set the max upload or download rate in KB/s. maxRate must be grater than
   * 41       * 0. If maxRate is zero, it means there is no bandwidth limit.
   * 42       *
   * 43       *  @param  maxRate
   * 44       *            If maxRate is zero, it means there is no bandwidth limit.
   * 45       *  @throws  IllegalArgumentException
   * 46
   */
  public synchronized void setMaxRate(int maxRate)
          throws IllegalArgumentException {
    if (maxRate < 0) {
      throw new IllegalArgumentException(" maxRate can not less than 0 ");
    }
    if (this.maxRate == maxRate) { // equal
      return;
    }
    this.maxRate = maxRate < 0 ? 0 : maxRate;
    if (maxRate == 0)
      this.timeCostPerChunk = 0;
    else
      this.timeCostPerChunk = (1000000000l * CHUNK_LENGTH)
              / (this.maxRate * KB);
  }

  /**
   * 61       * Next 1 byte should do bandwidth limit.
   * 62
   */
  public synchronized void limitNextBytes() {
    this.limitNextBytes(1);
  }

  /**
   * 68       * Next len bytes should do bandwidth limit
   * 69       *
   * 70       *  @param  len
   * 71
   */
  public synchronized void limitNextBytes(int len) {
    this.bytesWillBeSentOrReceive += len;

    /*  We have sent CHUNK_LENGTH bytes  */
    while (this.bytesWillBeSentOrReceive > CHUNK_LENGTH) {
      long nowTick = System.nanoTime();
      long missedTime = this.timeCostPerChunk
              - (nowTick - this.lastPieceSentOrReceiveTick);
      if (missedTime > 0) {
        try {
          Thread.sleep(missedTime / 1000000,
                  (int) (missedTime % 1000000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      this.bytesWillBeSentOrReceive -= CHUNK_LENGTH;
      this.lastPieceSentOrReceiveTick = nowTick
              + (missedTime > 0 ? missedTime : 0);
    }
  }
}
